package net.aiion.weave.impl.cards;

import net.aiion.weave.impl.cards.xml.CardExpressionTransformation;
import net.aiion.weave.impl.cards.xml.ComponentTransformation;
import net.aiion.weave.impl.cards.xml.ContainerTransformation;
import net.aiion.weave.impl.cards.xml.ForTransformation;
import net.aiion.weave.impl.cards.xml.IfTransformation;
import net.aiion.weave.impl.cards.xml.XmlEventTransformation;
import net.aiion.weave.spi.bytecode.Bytecode;
import net.aiion.weave.spi.bytecode.BytecodeGenerator;
import net.aiion.weave.spi.cards.Card;
import net.aiion.weave.spi.cards.CardException;
import net.aiion.weave.spi.cards.CardProperties;
import net.aiion.weave.spi.cards.CardService;
import net.aiion.weave.spi.cards.expressions.CardExpressionResolverService;
import net.aiion.weave.spi.injection.annotations.Inject;
import net.aiion.weave.spi.naming.Context;
import net.aiion.weave.spi.naming.UserContext;
import net.aiion.weave.spi.naming.annotations.Named;
import net.aiion.weave.spi.naming.annotations.NotScoped;
import net.aiion.weave.spi.resources.Resource;
import net.aiion.weave.spi.xml.XmlGenerator;
import net.aiion.weave.spi.xml.XmlSource;
import net.aiion.weave.spi.xml.XmlSourceFactory;

@NotScoped
@Named
public class CardServiceImpl implements CardService
{
    @Inject
    private Context context;

    public CardServiceImpl() {
    }

    @Override
    public Card findCard(final Object aComponent, final Resource aResource) {
        final String cardClassName = generateBinaryName(aResource);

        final Class<?> cardClass = Bytecode.findClassByName(cardClassName);
        if (cardClass == null) {
            generateCard(aResource);
        }

        try {
            return Bytecode.findClassByName(cardClassName).asSubclass(Card.class).newInstance();
        } catch (final InstantiationException | IllegalAccessException e) {
            throw new CardException(e);
        }
    }

    @Override
    public void renderCard(final Object aComponent, final Resource aResource, final XmlGenerator aXmlGenerator) {
        final UserContext userContext = context.createUserContext();
        final CardExpressionResolverService cardExpressionResolverService = userContext
            .lookup(CardExpressionResolverService.class);

        if (aComponent instanceof CardProperties) {
            userContext.bind(CardProperties.class.getName(), (CardProperties) aComponent);
        }

        findCard(aComponent, aResource).processCard(
            new CardContextImpl(context, cardExpressionResolverService, aXmlGenerator));
    }

    private void generateCard(final Resource aResource) {
        final BytecodeGenerator bytecodeGenerator = Bytecode.createGenerator(Bytecode.createWriter());

        final XmlSource xmlSource = XmlSourceFactory.getInstance().createReader(aResource);
        xmlSource.setProperty("bytecodeGenerator", bytecodeGenerator);
        xmlSource.setProperty("internalName", generateInternalName(aResource));
        xmlSource.setProperty("className", generateBinaryName(aResource));

        // TODO Finish this stuff
        xmlSource.addSink(new ContainerTransformation());
        xmlSource.addSink(new CardExpressionTransformation());
        xmlSource.addSink(new ComponentTransformation());
        xmlSource.addSink(new IfTransformation());
        xmlSource.addSink(new ForTransformation());
        xmlSource.addSink(new XmlEventTransformation());

        // xmlReader.addSink(new BindableTransformation()); // UI Binder // XXX

        xmlSource.startSourcing();
    }

    private static String generateBinaryName(final Resource aResource) {
        return new StringBuilder(aResource.getName().substring(0, aResource.getName().lastIndexOf('.'))
            .replace('.', '_')).append('$').append(Integer.toHexString(aResource.hashCode())).toString();
    }

    private static String generateInternalName(final Resource aResource) {
        return new StringBuilder(aResource.getName().substring(0, aResource.getName().lastIndexOf('.'))
            .replace('.', '_')).append('$').append(Integer.toHexString(aResource.hashCode())).toString()
            .replace('.', '/');
    }
}
